<html lang="en-US">

<head>
    
<title>用 Go 来了解一下 Redis 通讯协议 - 花落雨忧</title>

<meta property="og:title" content="用 Go 来了解一下 Redis 通讯协议 - 花落雨忧">



    



    
    <meta property="description" content="Go、PHP、Java&amp;hellip; 都有那么多包来支撑你使用 Redis，那你是否有想过
有了服务端，有了客户端，他们俩是怎样通讯，又是基于什么通讯协议做出交互的呢？
[&amp;hellip;] 基于我们的目的，本文主要讲解和实践 Redis 的通讯协议
Redis 的客户端和服务端是通过 TCP 连接来进行数据交互， 服务器默认的端口号为 6379
客户端和服务器发送的命令或数据一律以 \r\n &amp;hellip;">
    <meta property="og:description" content="Go、PHP、Java&amp;hellip; 都有那么多包来支撑你使用 Redis，那你是否有想过
有了服务端，有了客户端，他们俩是怎样通讯，又是基于什么通讯协议做出交互的呢？
[&amp;hellip;] 基于我们的目的，本文主要讲解和实践 Redis 的通讯协议
Redis 的客户端和服务端是通过 TCP 连接来进行数据交互， 服务器默认的端口号为 6379
客户端和服务器发送的命令或数据一律以 \r\n &amp;hellip;">
    






<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<meta name="viewport" content="width=device-width,initial-scale=1">

<link rel="shortcut icon" href="https://www.lican.asia/logo/logo.png" type="image/x-icon" />



<link rel="stylesheet" href="/css/style.min.css" />

<link rel="stylesheet" href="/css/reset.min.css" />




<script src="https://www.lican.asia/js/highlight.min.js"></script>

<script>
hljs.configure({ ignoreUnescapedHTML: true })
hljs.highlightAll();
</script>


<script src="https://www.lican.asia/js/jquery.min.js"></script>




<link href="https://www.lican.asia/css/hugo-code.min.css" rel="stylesheet" />



    <style>
        .post-content img {
            max-width: 400px;
        }
    </style>
</head>

<body id="period" class="home blog">
    <a class="skip-content" href="#main">Press "Enter" to skip to content</a>
    <div id="overflow-container" class="overflow-container">
        <header class="site-header" id="site-header" role="banner">
    <div class="max-width">
        <div id="title-container" class="title-container">
            <div id="site-title" class="site-title"><a href="/">花落雨忧</a></div>
            <p class="tagline">思所及 力所行 方为真.</p>
        </div>
        
        <div id="menu-primary-container" class="menu-primary-container">
            <div class="icon-container">
            </div>
            <div id="menu-primary" class="menu-container menu-primary" role="navigation">
                <nav class="menu">
                    <ul id="menu-primary-items" class="menu-primary-items">
                        
                        
                        <li id="menu-item-0"
                            class="menu-item menu-item-type-custom menu-item-object-custom ">
                            <a href="/posts" aria-current="page" tabindex="0">首页</a></li>
                        
                        <li id="menu-item-1"
                            class="menu-item menu-item-type-custom menu-item-object-custom ">
                            <a href="/tech/" aria-current="page" tabindex="1">技术文档</a></li>
                        
                        <li id="menu-item-2"
                            class="menu-item menu-item-type-custom menu-item-object-custom ">
                            <a href="/article/" aria-current="page" tabindex="2">文章</a></li>
                        
                        <li id="menu-item-3"
                            class="menu-item menu-item-type-custom menu-item-object-custom ">
                            <a href="/project/" aria-current="page" tabindex="3">项目</a></li>
                        
                        <li id="menu-item-4"
                            class="menu-item menu-item-type-custom menu-item-object-custom ">
                            <a href="/about/" aria-current="page" tabindex="4">关于</a></li>
                        
                    </ul>
                </nav>
            </div>
        </div>
    </div>
</header>

        <div id="primary-container" class="primary-container">
            <div class="max-width">
                <section id="main" class="main" role="main">
                    <div id="loop-container" class="loop-container">
                        <div
                            class="post type-post status-publish format-standard hentry entry">
                            <article>
                                <div class="post-container">
                                    <div class="post-header">
                                        <h2 class="post-title">
                                            <a href="/posts/posts/go/talk/2018-06-07-go-redis-protocol/">用 Go 来了解一下 Redis 通讯协议</a>
                                        </h2>
                                        
                                        <div class="post-byline">Published on
                                            <a class="date" href="javascript:;">2018/06/07</a>
                                            
                                            
                                            
                                            By 
                                            <a class="author" href="javascript:;">煎鱼</a></div>
                                            
                                        
                                    </div>
                                    <div class="post-content">
                                        <p>Go、PHP、Java&hellip; 都有那么多包来支撑你使用 Redis，那你是否有想过</p>
<p>有了服务端，有了客户端，他们俩是怎样通讯，又是基于什么通讯协议做出交互的呢？</p>
<h2 id="介绍">介绍</h2>
<p>基于我们的目的，本文主要讲解和实践 Redis 的通讯协议</p>
<p>Redis 的客户端和服务端是通过 TCP 连接来进行数据交互， 服务器默认的端口号为 6379</p>
<p>客户端和服务器发送的命令或数据一律以 \r\n （CRLF）结尾（这是一条约定）</p>
<h2 id="协议">协议</h2>
<p>在 Redis 中分为<strong>请求</strong>和<strong>回复</strong>，而请求协议又分为新版和旧版，新版统一请求协议在 Redis 1.2 版本中引入，最终在 Redis 2.0 版本成为 Redis 服务器通信的标准方式</p>
<p>本文是基于新版协议来实现功能，不建议使用旧版（1.2 挺老旧了）。如下是新协议的各种范例：</p>
<h3 id="请求协议">请求协议</h3>
<p>1、 格式示例</p>
<pre tabindex="0"><code>*&lt;参数数量&gt; CR LF
$&lt;参数 1 的字节数量&gt; CR LF
&lt;参数 1 的数据&gt; CR LF
...
$&lt;参数 N 的字节数量&gt; CR LF
&lt;参数 N 的数据&gt; CR LF
</code></pre><p>在该协议下所有发送至 Redis 服务器的参数都是二进制安全（binary safe）的</p>
<p>2、打印示例</p>
<pre tabindex="0"><code>*3
$3
SET
$5
mykey
$7
myvalue
</code></pre><p>3、实际协议值</p>
<pre tabindex="0"><code>&#34;*3\r\n$3\r\nSET\r\n$5\r\nmykey\r\n$7\r\nmyvalue\r\n&#34;
</code></pre><p>这就是 Redis 的请求协议规范，按照范例1编写客户端逻辑，最终发送的是范例3，相信你已经有大致的概念了，Redis 的协议非常的简洁易懂，这也是好上手的原因之一，你可以想想协议这么定义的好处在哪？</p>
<h3 id="回复">回复</h3>
<p>Redis 会根据你请求协议的不同（执行的命令结果也不同），返回多种不同类型的回复。在这个回复“协议”中，可以通过检查第一个字节，确定这个回复是什么类型，如下：</p>
<ul>
<li>状态回复（status reply）的第一个字节是 &ldquo;+&rdquo;</li>
<li>错误回复（error reply）的第一个字节是 &ldquo;-&rdquo;</li>
<li>整数回复（integer reply）的第一个字节是 &ldquo;:&rdquo;</li>
<li>批量回复（bulk reply）的第一个字节是 &ldquo;$&rdquo;</li>
<li>多条批量回复（multi bulk reply）的第一个字节是 &ldquo;*&rdquo;</li>
</ul>
<p>有了回复的头部标识，结尾的 CRLF，你可以大致猜想出回复“协议”是怎么样的，但是实践才能得出真理，斎知道怕是你很快就忘记了 😀</p>
<h2 id="实践">实践</h2>
<h3 id="与-redis-服务器交互">与 Redis 服务器交互</h3>
<pre tabindex="0"><code>package main

import (
	&#34;log&#34;
	&#34;net&#34;
	&#34;os&#34;

	&#34;github.com/EDDYCJY/redis-protocol-example/protocol&#34;
)

const (
	Address = &#34;127.0.0.1:6379&#34;
	Network = &#34;tcp&#34;
)

func Conn(network, address string) (net.Conn, error) {
	conn, err := net.Dial(network, address)
	if err != nil {
		return nil, err
	}

	return conn, nil
}

func main() {
        // 读取入参
	args := os.Args[1:]
	if len(args) &lt;= 0 {
		log.Fatalf(&#34;Os.Args &lt;= 0&#34;)
	}
    
        // 获取请求协议
	reqCommand := protocol.GetRequest(args)
	
	// 连接 Redis 服务器
	redisConn, err := Conn(Network, Address)
	if err != nil {
		log.Fatalf(&#34;Conn err: %v&#34;, err)
	}
	defer redisConn.Close()
    
        // 写入请求内容
	_, err = redisConn.Write(reqCommand)
	if err != nil {
		log.Fatalf(&#34;Conn Write err: %v&#34;, err)
	}
    
        // 读取回复
	command := make([]byte, 1024)
	n, err := redisConn.Read(command)
	if err != nil {
		log.Fatalf(&#34;Conn Read err: %v&#34;, err)
	}
    
        // 处理回复
	reply, err := protocol.GetReply(command[:n])
	if err != nil {
		log.Fatalf(&#34;protocol.GetReply err: %v&#34;, err)
	}
    
        // 处理后的回复内容
	log.Printf(&#34;Reply: %v&#34;, reply)
	// 原始的回复内容
	log.Printf(&#34;Command: %v&#34;, string(command[:n]))
}
</code></pre><p>在这里我们完成了整个 Redis 客户端和服务端交互的流程，分别如下：</p>
<p>1、读取命令行参数：获取执行的 Redis 命令</p>
<p>2、获取请求协议参数</p>
<p>3、连接 Redis 服务器，获取连接句柄</p>
<p>4、将请求协议参数写入连接：发送请求的命令行参数</p>
<p>5、从连接中读取返回的数据：读取先前请求的回复数据</p>
<p>6、根据回复“协议”内容，处理回复的数据集</p>
<p>7、输出处理后的回复内容及原始回复内容</p>
<h3 id="请求">请求</h3>
<pre tabindex="0"><code>func GetRequest(args []string) []byte {
	req := []string{
		&#34;*&#34; + strconv.Itoa(len(args)),
	}

	for _, arg := range args {
		req = append(req, &#34;$&#34;+strconv.Itoa(len(arg)))
		req = append(req, arg)
	}

	str := strings.Join(req, &#34;\r\n&#34;)
	return []byte(str + &#34;\r\n&#34;)
}
</code></pre><p>通过对 Redis 的请求协议的分析，可得出它的规律，先加上标志位，计算参数总数量，再循环合并各个参数的字节数量、值就可以了</p>
<h3 id="回复-1">回复</h3>
<pre tabindex="0"><code>func GetReply(reply []byte) (interface{}, error) {
	replyType := reply[0]
	switch replyType {
	case StatusReply:
		return doStatusReply(reply[1:])
	case ErrorReply:
		return doErrorReply(reply[1:])
	case IntegerReply:
		return doIntegerReply(reply[1:])
	case BulkReply:
		return doBulkReply(reply[1:])
	case MultiBulkReply:
		return doMultiBulkReply(reply[1:])
	default:
		return nil, nil
	}
}

func doStatusReply(reply []byte) (string, error) {
	if len(reply) == 3 &amp;&amp; reply[1] == &#39;O&#39; &amp;&amp; reply[2] == &#39;K&#39; {
		return OkReply, nil
	}

	if len(reply) == 5 &amp;&amp; reply[1] == &#39;P&#39; &amp;&amp; reply[2] == &#39;O&#39; &amp;&amp; reply[3] == &#39;N&#39; &amp;&amp; reply[4] == &#39;G&#39; {
		return PongReply, nil
	}

	return string(reply), nil
}

func doErrorReply(reply []byte) (string, error) {
	return string(reply), nil
}

func doIntegerReply(reply []byte) (int, error) {
	pos := getFlagPos(&#39;\r&#39;, reply)
	result, err := strconv.Atoi(string(reply[:pos]))
	if err != nil {
		return 0, err
	}

	return result, nil
}

...
</code></pre><p>在这里我们对所有回复类型进行了分发，不同的回复标志位对应不同的处理方式，在这里需求注意几项问题，如下：</p>
<p>1、当请求的值不存在，会将特殊值 -1 用作回复</p>
<p>2、服务器发送的所有字符串都由 CRLF 结尾</p>
<p>3、多条批量回复是可基于批量回复的，要注意理解</p>
<p>4、无内容的多条批量回复是存在的</p>
<p>最重要的是，对不同回复的规则的把控，能够让你更好的理解 Redis 的请求、回复的交互过程 👌</p>
<h2 id="小结">小结</h2>
<p>写这篇文章的起因，是因为常常在使用 Redis 时，只是用，你不知道它是基于什么样的通讯协议来通讯，这样的感觉是十分难受的</p>
<p>通过本文的讲解，我相信你已经大致了解 Redis 客户端是怎么样和服务端交互，也清楚了其所用的通讯原理，希望能够对你有所帮助！</p>
<p>最后，如果想详细查看代码，右拐项目地址：https://github.com/EDDYCJY/redis-protocol-example</p>
<p>如果对你有所帮助，欢迎点个 Star 👍</p>
<h2 id="参考">参考</h2>
<ul>
<li><a href="http://doc.redisfans.com/topic/protocol.html">通信协议</a></li>
</ul>


                                        
                                        
                                        
                                        <div class="rp4wp-related-posts">
                                            <h3>相关文章</h3>
                                            <ul>
                                                
                                                <li>
                                                    <div class="rp4wp-related-post-content">
                                                        <a
                                                            href="https://www.lican.asia/posts/posts/go/gin/2018-06-02-application-redis/">「连载十三」优化你的应用结构和实现Redis缓存</a>
                                                    </div>
                                                </li>
                                                
                                                <li>
                                                    <div class="rp4wp-related-post-content">
                                                        <a
                                                            href="https://www.lican.asia/posts/posts/go/gin/2018-05-27-config-upload/">「连载十二」优化配置结构及实现图片上传</a>
                                                    </div>
                                                </li>
                                                
                                                <li>
                                                    <div class="rp4wp-related-post-content">
                                                        <a
                                                            href="https://www.lican.asia/posts/posts/go/talk/2018-05-21-go-fake-useragent/">Go 的 fake-useragent 了解一下</a>
                                                    </div>
                                                </li>
                                                
                                                <li>
                                                    <div class="rp4wp-related-post-content">
                                                        <a
                                                            href="https://www.lican.asia/posts/posts/go/gin/2018-04-29-cron/">「连载十一」Cron定时任务</a>
                                                    </div>
                                                </li>
                                                
                                                <li>
                                                    <div class="rp4wp-related-post-content">
                                                        <a
                                                            href="https://www.lican.asia/posts/posts/go/crawler/2018-04-28-go2018/">了解一下Golang的市场行情</a>
                                                    </div>
                                                </li>
                                                
                                            </ul>
                                        </div>
                                        
                                        
                                    </div>

                                    
                                    
                                    

                                    
                                    <div class="post-meta">
                                        
                                        
                                        <div class="post-tags">
                                            <ul>
                                            
                                            <li>
                                                <a href="/tags/go" title="View all posts tagged match">go</a>
                                            </li>
                                            
                                            </ul>
                                        </div>
                                        
                                        
                                        <nav class="further-reading">
                                            
                                            <div class="previous">
                                                <span>&lt;&lt; Prev</span>
                                                <a href="https://www.lican.asia/posts/posts/go/gin/2018-06-02-application-redis/"
                                                    rel="prev">「连载十三」优化你的应用结构和实现Redis缓存</a> </div>
                                            
                                            
                                            <div class="next">
                                                <span>Next >></span>
                                                <a href="https://www.lican.asia/posts/posts/go/gin/2018-06-14-excel/">「连载十四」实现导出、导入 Excel</a> 
                                            </div>
                                            
                                        </nav>
                                    </div>
                                    
                                    

                                    
                                    
                                    

                                </div>
                            </article>
                        </div>
                    </div>
                </section>
                <aside class="sidebar sidebar-primary" id="sidebar-primary" role="complementary">
    <h1 class="screen-reader-text">Sidebar</h1>
    

    
    
    <section id="text-2" class="widget widget_text">
        <div class="textwidget">
            
            <div id="profile">
                <div id="profile_picture"><img src="https://www.lican.asia/logo/logo.png"></div>
                <div id="profile_intro">
                    <p><span class="name">Lican</span></p>
                    <p class="intro">全栈开发者，爱好造轮子。</p>
                </div>
            </div>
            
            <p>
                <script type="text/javascript">
                    (function ($) {
                        $(document).ready(function () {
                            var menuPrimaryContainer = $('#menu-primary-container');
                            var profile = $('#text-2');
                            $('#toggle-navigation').click(function () {
                                if (menuPrimaryContainer.hasClass('open')) {
                                    profile.removeClass('open');
                                } else {
                                    profile.addClass('open');
                                }
                            });
                        });
                    })(jQuery);
                </script>
            </p>
        </div>
    </section>
    
    
    
    
    
    <section id="text-5" class="widget widget_text">
        <h2 class="widget-title">开源项目</h2>
        <div class="textwidget">
            <div id="projects" style="line-height: 22px;">
                
                <a href="https://github.com/idoubi/gonews"
                    target="_blank">gonews</a>: &nbsp;Daily news for golang<br>
                
                <a href="https://github.com/idoubi/sql2struct"
                    target="_blank">sql2struct</a>: &nbsp;Generate go struct according to SQL<br>
                
                <a href="https://github.com/idoubi/goz"
                    target="_blank">goz</a>: &nbsp;Request library used in golang<br>
                
        </div>
    </section>
    
    

    
    
    
    
    <section id="qrcode" class="widget widget_media_image">
        <h2 class="widget-title">微信公众号</h2>
        <img width="258" height="258"
            src="https://www.lican.asia/wechat/lican.png"
            class="image wp-image-5514  attachment-full size-full" alt=""
            style="max-width: 100%; height: auto;"
            sizes="(max-width: 258px) 100vw, 258px">
    </section>
    
    

    
    
    

    
    
    

    
    
    
    
</aside>
            </div>
        </div>

        <footer id="site-footer" class="site-footer" role="contentinfo">
    <div class="max-width">
    </div>
    <div class="footer">
        <div id="footercontent">
            © lican.asia All rights reserved<br/>
            Built with Hugo Theme <a href="https://github.com/idoubi/hugo-theme-period" target="_blank">Period</a>
        </div>
    </div>
</footer>

<script>
    var _hmt = _hmt || [];
    (function() {
      var hm = document.createElement("script");
      hm.src = "https://hm.baidu.com/hm.js?e8351b6d4626d5881d439ea1f6184baa";
      var s = document.getElementsByTagName("script")[0]; 
      s.parentNode.insertBefore(hm, s);
    })();
</script>
    
    
    </div>
    
</body>

</html>